文档翻译|KVO :Introduction to Key-Value Observing Programming Guide

KVO 官方翻译文档

KVC Collection Operators

注册方式

addObserver:(NSObject )observer forKeyPath:(NSString )keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

The context pointer can be a C pointer or an object reference. The context pointer can be used as a unique identifier to determine the change that is being observed, or to provide some other data to the observer.

Note:
The key-value observing addObserver:forKeyPath:options:context: method does not maintain strong references to the observing object, the observed objects, or the context. You should ensure that you maintain strong references to the observing, and observed, objects, and the context as necessary.

1
2
3
4
5
6
7
8
9
10
- (void)registerAsObserver {
/*
Register 'inspector' to receive change notifications for the "openingBalance" property of the 'account' object and specify that both the old and new values of "openingBalance" should be provided in the observe… method.
*/
[account addObserver:inspector
forKeyPath:@"openingBalance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
}

如果Value属性是个对象,直接传递,如果是the property is a scalar or a C structure,通过KVC取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqual:@"openingBalance"]) {
[openingBalanceInspectorField setObjectValue:
[change objectForKey:NSKeyValueChangeNewKey]];
}
/*
Be sure to call the superclass's implementation *if it implements it*.
NSObject does not implement the method.
*/
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}

如果context是个对象,在remove前必须保持强持有

1
[observedObject removeObserver:inspector forKeyPath:@"openingBalance"];

KVO Compliance

前提

  • 该类提供必要的方法使这个property 是KVC Compliant的。
  • 这个类能正确发出KVO change notification;
  • 若某个Property依赖于其他几个Property(称为dependent keys),都应该被注册好.

自动KVO

如果我们使用Accessor方法或者KVC 方法(setValue:forKey setValue:forKeyPath等)改变属性的值都会自动发出notification。

手动KVO

Manual change notification往往配合automaticallyNotifiesObserversForKey:一起使用。如果一个Property已经是KVC Compliant的了,那意味着这个Property已经默认支持automatic notification了,我们要让其做manaul notification,就要覆盖automaticallyNotifiesObserversForKey:,使其不要多automatic notification。但是如果一个instance variable或者attribute并不是KVC Compliance的,而它仍然希望发出notification,那么就不需要覆盖automaticallyNotifiesObserversForKey:了。下面是一个automaticallyNotifiesObserversForKey:的例子:

1
2
3
4
5
6
7
8
9
10
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"openingBalance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}

将automatic notification关闭后,就需要调用willChangeValueForKey:didChangeValueForKey:来实现通知行为,如果一个操作导致多个key发生变化,需要嵌套调用。

1
2
3
4
5
6
7
8
9
10
- (void)setOpeningBalance:(double)theBalance {
if (theBalance != _openingBalance) { //优化
[self willChangeValueForKey:@"openingBalance"];
[self willChangeValueForKey:@"itemChanged"];
_openingBalance = theBalance;
_itemChanged = _itemChanged+1;
[self didChangeValueForKey:@"itemChanged"];
[self didChangeValueForKey:@"openingBalance"];
}
}

>
In the case of an ordered to-many relationship, you must specify not only the key that changed, but also the type of change and the indexes of the objects involved. The type of change is an NSKeyValueChange that specifies NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, or NSKeyValueChangeReplacement. The indexes of the affected objects are passed as an NSIndexSet object.

1
2
3
4
5
6
7
8
- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
[self willChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
// Remove the transaction objects at the specified indexes.
[self didChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
}

Registering Dependent Keys

property 的值依赖于其他对象的多个属性时,如何解决

对某个属性(应该是说有限长度的属性)计算属性

1
2
3
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}

一是可以覆盖keyPathsForValuesAffectingValueForKey:(NSString *)key方法,先调用super,在添加keyPath

1
2
3
4
5
6
7
8
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"]) {
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}

二是也可以通过遵循 naming convention 的 class method 来完成同样的功能用keyPathsForValuesAffecting方式生成keyPath

1
2
3
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}

>
Note:
You can’t override the keyPathsForValuesAffectingValueForKey: method when you add a computed property to an existing class using a category, because you’re not supposed to override methods in categories. In that case, implement a matching keyPathsForValuesAffecting class method to take advantage of this mechanism.

对多:(应该是指不定长度)

情景:某部门有多个员工,你需要有这个部门所有员工的工资属性,这时不能用keyPathsForValuesAffectingTotalSalary 并且返回employees.salary作为key

  • 你可以多其父类(部门)进行KVO,对相关的孩子(员工)进行观察。并且在observeValueForKeyPath:ofObject:change:context: 方法中更新变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == totalSalaryContext) {
[self updateTotalSalary];
}
else
// deal with other observations and/or invoke super..
}
- (void)updateTotalSalary {
[self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
if (totalSalary != newTotalSalary) {
[self willChangeValueForKey:@"totalSalary"];
_totalSalary = newTotalSalary;
[self didChangeValueForKey:@"totalSalary"];
}
}
- (NSNumber *)totalSalary {
return _totalSalary;
}
  • Core Data
    If you’re using Core Data, you can register the parent with the application’s notification center as an observer of its managed object context. The parent should respond to relevant change notifications posted by the children in a manner similar to that for key-value observing

实现原理:

自动KVO是通过 isa-swizzling实现的

upload successful

实例isa指针之乡对象类(Class A),里面有类实现方法及数据。当有观察者注册对象的属性后,isa指向SubClassA,因此实际的isa指针并不能真正反映出实例的真实类。

因此,不能依靠isa决定类的关系,而通过class method的方法。

原文地址